#include "XiolController.h"

#include <assert.h>
#include <algorithm>

#include "XiolTime.h"
#include "XiolControllerState.h"
#include "XiolVibrationMotorState.h"

namespace Xiol
{

const char*	Controller::mSubTypeName[SubType_Count] = 
		{
			"XBox 360 Gamepad",
			"XBox 360 Wheel",
			"XBox 360 Arcade Stick",
			"XBox 360 Flight Stick",
			"XBox 360 Dance Pad",
			"XBox 360 Guitar",
			"XBox 360 Drum Kit",
			"XBox 360 Arcade Pad"
		};

const char*	Controller::mComponentTypeName[ComponentType_Count] = 
		{
			"Button",
			"Trigger",
			"Thumbstick",
			"VibrationMotor"
		};

const char*	Controller::mButtonName[Button_Count] = 
		{ 
			"DPad Up", 
			"DPad Down",
			"DPad Left",
			"DPad Right",
			"Start",
			"Back",
			"Left Thumbstick",
			"Right Thumbstick",
			"Left Shoulder",
			"Right Shoulder",
			"A",	
			"B",
			"X",
			"Y"
		};

const WORD	Controller::mButtonXInputID[Button_Count] = 
		{
			XINPUT_GAMEPAD_DPAD_UP,
			XINPUT_GAMEPAD_DPAD_DOWN,      
			XINPUT_GAMEPAD_DPAD_LEFT,      
			XINPUT_GAMEPAD_DPAD_RIGHT,     
			XINPUT_GAMEPAD_START,     
			XINPUT_GAMEPAD_BACK,         
			XINPUT_GAMEPAD_LEFT_THUMB,
			XINPUT_GAMEPAD_RIGHT_THUMB,    
			XINPUT_GAMEPAD_LEFT_SHOULDER,  
			XINPUT_GAMEPAD_RIGHT_SHOULDER, 
			XINPUT_GAMEPAD_A,
			XINPUT_GAMEPAD_B,             
			XINPUT_GAMEPAD_X,             
			XINPUT_GAMEPAD_Y
		};

const char*	Controller::mTriggerName[Trigger_Count] = 
		{
			"Left Trigger",
			"Right Trigger"
		};

const char*	Controller::mThumbstickName[Thumbstick_Count] =
		{
			"Left Thumstick",
			"Right Thumstick"
		};

const char*	Controller::mVibrationMotorName[VibrationMotor_Count] = 
		{
			"Left Motor",
			"Right Motor"
		};

const char*	Controller::mBatteryTypeName[BatteryType_Count] = 
		{
			"Unknown",
			"Alkaline",
			"NiMH"			
		};

const char*	Controller::mBatteryName[Battery_Count]  = 
		{
			"Controller",
			"Headset"
		};

Controller::Controller( DWORD controllerIndex, const XINPUT_CAPABILITIES& capabilities )
	:	mControllerIndex(controllerIndex),
		mSubType(SubType_Gamepad),
		mNextBatteryUpdateTime(0),
		mLastPacketNumber(0),
		mHasVoiceSupport(false)
{
	// Clearing capability and state values
	for ( int i=0; i<Button_Count; ++i )
	{
		mHasButton[i] = false;
		mIsButtonPressed[i] = 0;
	}

	for ( int i=0; i<Trigger_Count; ++i )
	{
		mHasTrigger[i] = false;
		mTriggerPosition[i] = 0;
	}

	for ( int i=0; i<Thumbstick_Count; ++i )
	{
		mHasThumbstick[i] = false;
		mThumbstickXPosition[i] = 0;
		mThumbstickYPosition[i] = 0;
	}

	for ( int i=0; i<VibrationMotor_Count; ++i )
	{
		mHasVibrationMotor[i] = false;
		mVibrationMotorSpeed[i] = 0;
	}

	// Clear battery information
	for ( int i=0; i<Battery_Count; ++i )
		clearBatteryInformation(static_cast<BatteryID>(i));
	
	// Update it immediately at startup
	updateBatteryInformation();

	// Schedule next battery update 
	mNextBatteryUpdateTime = Time::getTimeAsMilliseconds() + mBatteryUpdateIntervalInMs;

	// SubType
	mSubType = xinputSubTypeToSubType( capabilities.SubType );

	// Misc. capability flags
	WORD flags = capabilities.Flags ;
	mHasVoiceSupport = (flags & XINPUT_CAPS_VOICE_SUPPORTED)!=0;
	// The following flags are not supported on Windows:
	// - XINPUT_CAPS_FFB_SUPPORTED 
	// - XINPUT_CAPS_WIRELESS
	// - XINPUT_CAPS_PMD_SUPPORTED 
	// - XINPUT_CAPS_NO_NAVIGATION

	// Available buttons, triggers and thumbsticks
	ControllerState availableComponents( capabilities.Gamepad );
	for ( int i=0; i<Button_Count; ++i )
		mHasButton[i] = availableComponents.isButtonPressed( mButtonXInputID[i] );
	for ( int i=0; i<Trigger_Count; ++i )
		mHasTrigger[i] = (availableComponents.getTriggerPosition(i)!=0);	// We're assuming here that if the value is not 0 we've got the corresponding trigger component. Proper tests are needed on exotic controllers
	for ( int i=0; i<Thumbstick_Count; ++i )
	{
		SHORT x = 0;
		SHORT y = 0;
		availableComponents.getThumbstickPosition( i, x, y );
		mHasThumbstick[i] = (x!=0 && y!=0);									// Same thing with thumbsticks here
	}

	// Available vibration motors
	VibrationMotorState availableVibrationMotors( capabilities.Vibration );
	for ( int i=0; i<VibrationMotor_Count; ++i )
		mHasVibrationMotor[i] = (availableVibrationMotors.getVibrationMotorSpeed(i)!=0);	// And vibration motors

	// Ensure the motors are stopped
	for ( int i=0; i<VibrationMotor_Count; ++i )
		setVibrationMotorSpeed( static_cast<VibrationMotorID>(i), 0 );
}

Controller::~Controller()
{
	// Ensure the motors are stopped
	for ( int i=0; i<VibrationMotor_Count; ++i )
		setVibrationMotorSpeed( static_cast<VibrationMotorID>(i), 0 );
}

Controller::SubType Controller::xinputSubTypeToSubType( int xinputSubType )
{
#ifndef XINPUT_USE_9_1_0
	switch ( xinputSubType )
	{
		case XINPUT_DEVSUBTYPE_GAMEPAD : return SubType_Gamepad;
		case XINPUT_DEVSUBTYPE_WHEEL : return SubType_Wheel;
		case XINPUT_DEVSUBTYPE_ARCADE_STICK : return SubType_ArcadeStick;
// It seems that XInput 1.4 fixes the typo in XINPUT_DEVSUBTYPE_FLIGHT_SICK 
#ifdef XINPUT_DEVSUBTYPE_FLIGHT_STICK
		case XINPUT_DEVSUBTYPE_FLIGHT_STICK : return SubType_FlightStick;	
#else
		case XINPUT_DEVSUBTYPE_FLIGHT_SICK : return SubType_FlightStick;	
#endif
		case XINPUT_DEVSUBTYPE_DANCE_PAD : return SubType_DancePad;
		case XINPUT_DEVSUBTYPE_GUITAR : return SubType_Guitar;
		case XINPUT_DEVSUBTYPE_DRUM_KIT : return SubType_DrumKit;

		// The following are not supported on Windows:
		// - XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE 
		// - XINPUT_DEVSUBTYPE_GUITAR_BASS
		// - XINPUT_DEVSUBTYPE_ARCADE_PAD
	}
	assert(false);
#endif
	return SubType_Gamepad;
}

void Controller::setVibrationMotorSpeed( VibrationMotorID motorID, WORD speed )
{
	if ( !hasVibrationMotor(motorID) )
		return;

	if ( mVibrationMotorSpeed[motorID]==speed )
		return;
	
	VibrationMotorState vibrationMotorSpeed;
	for ( int i=0; i<VibrationMotor_Count; ++i )
		vibrationMotorSpeed.setVibrationMotorSpeed( i, mVibrationMotorSpeed[i] );
	vibrationMotorSpeed.setVibrationMotorSpeed( motorID, speed );
	
	XINPUT_VIBRATION vibrationStruct;
	ZeroMemory( &vibrationStruct, sizeof(XINPUT_VIBRATION) );
	vibrationStruct.wLeftMotorSpeed = vibrationMotorSpeed.getVibrationMotorSpeed(VibrationMotor_Left);
	vibrationStruct.wRightMotorSpeed = vibrationMotorSpeed.getVibrationMotorSpeed(VibrationMotor_Right);

	DWORD dwResult = XInputSetState( getControllerIndex(), &vibrationStruct );
	if ( dwResult!=ERROR_SUCCESS )
		return;			// Failed to change the value of the motors

	for ( int i=0; i<VibrationMotor_Count; ++i )
		mVibrationMotorSpeed[i] = vibrationMotorSpeed.getVibrationMotorSpeed(i);

	// Notify
	for ( Listeners::iterator itr=mListeners.begin(); itr!=mListeners.end(); ++itr )
		(*itr)->onComponentChanged( this, ComponentType_VibrationMotor, motorID );
}

void Controller::setButtonPressed( ButtonID buttonID, bool pressed )
{
	assert( buttonID<Button_Count );

	if ( !hasButton(buttonID) )
		return;

	if ( mIsButtonPressed[buttonID]==pressed )
		return;

	mIsButtonPressed[buttonID] = pressed;

	// Notify
	for ( Listeners::iterator itr=mListeners.begin(); itr!=mListeners.end(); ++itr )
		(*itr)->onComponentChanged( this, ComponentType_Button, buttonID );
}

void Controller::setTriggerPosition( TriggerID triggerID, BYTE position )
{
	assert( triggerID<Trigger_Count );

	if ( !hasTrigger(triggerID) )
		return;

	if ( mTriggerPosition[triggerID]==position)
		return;
	
	mTriggerPosition[triggerID] = position;
		
	// Notify
	for ( Listeners::iterator itr=mListeners.begin(); itr!=mListeners.end(); ++itr )
		(*itr)->onComponentChanged( this, ComponentType_Trigger, triggerID );
}

void Controller::setThumbstickPosition( ThumbstickID thumbstickID, SHORT positionX, SHORT positionY )
{
	assert( thumbstickID<Thumbstick_Count );
	
	if ( !hasThumbstick(thumbstickID) )
		return;

	//if ( abs( mThumbstickXPosition[thumbstickID] - positionX ) <= mThumbstickSensitivity &&
	//	 abs( mThumbstickYPosition[thumbstickID] - positionY ) <= mThumbstickSensitivity  )
	//	return;

	mThumbstickXPosition[thumbstickID] = positionX;
	mThumbstickYPosition[thumbstickID] = positionY;

	// Notify
	for ( Listeners::iterator itr=mListeners.begin(); itr!=mListeners.end(); ++itr )
		(*itr)->onComponentChanged( this, ComponentType_Thumbstick, thumbstickID );
}

void Controller::update( const XINPUT_STATE& state )
{
	// Update components state
	DWORD currentPacketNumber = state.dwPacketNumber;
	if ( currentPacketNumber!=mLastPacketNumber )
	{
		mLastPacketNumber = currentPacketNumber;
		
		ControllerState gamepadWrapper( state.Gamepad );
		
		// Buttons
		for ( int i=0; i<Button_Count; ++i )
		{
			bool pressed = gamepadWrapper.isButtonPressed( mButtonXInputID[i] );
			setButtonPressed( static_cast<ButtonID>(i), pressed );
		}
	
		// Triggers
		setTriggerPosition( Trigger_Left, gamepadWrapper.getTriggerPosition(0) );
		setTriggerPosition( Trigger_Right, gamepadWrapper.getTriggerPosition(1) );

		// Thumbsticks
		SHORT positionX;
		SHORT positionY;
		gamepadWrapper.getThumbstickPosition( 0, positionX, positionY );
		setThumbstickPosition( Thumbstick_Left, positionX, positionY );
		gamepadWrapper.getThumbstickPosition( 1, positionX, positionY );
		setThumbstickPosition( Thumbstick_Right, positionX, positionY );
	}

	// Update battery state
	unsigned int time = Time::getTimeAsMilliseconds();
	if ( time>=mNextBatteryUpdateTime )
	{
		updateBatteryInformation();
		mNextBatteryUpdateTime = time + mBatteryUpdateIntervalInMs;
	}
}

void Controller::addListener( Listener* listener )
{
	assert(listener);
	mListeners.push_back(listener);
}

bool Controller::removeListener( Listener* listener )
{
	Listeners::iterator itr = std::find( mListeners.begin(), mListeners.end(), listener );
	if ( itr==mListeners.end() )
		return false;
	mListeners.erase( itr );
	return true;
}

BYTE Controller::getBatteryLevelMax()
{
	return BATTERY_LEVEL_FULL;
}	

void Controller::updateBatteryInformation()
{
#ifndef XINPUT_USE_9_1_0
	DWORD controllerIndex = getControllerIndex();
	DWORD dwResult;
	XINPUT_BATTERY_INFORMATION batteryInformation;

	ZeroMemory( &batteryInformation, sizeof(XINPUT_BATTERY_INFORMATION) );
	dwResult = XInputGetBatteryInformation( controllerIndex, BATTERY_DEVTYPE_GAMEPAD, &batteryInformation );
	if ( dwResult==ERROR_SUCCESS )
	{
		assert( batteryInformation.BatteryLevel>=0 && batteryInformation.BatteryLevel<=getBatteryLevelMax() );
		BatteryType batteryType;
		if ( xinputBatteryTypeToBatteryType(batteryInformation.BatteryType, batteryType) )
			setBatteryInformation( Battery_Controller, batteryType, batteryInformation.BatteryLevel );
		else
			clearBatteryInformation( Battery_Controller );
	}
	else
	{
		clearBatteryInformation( Battery_Controller );
	}

	ZeroMemory( &batteryInformation, sizeof(XINPUT_BATTERY_INFORMATION) );
	dwResult = XInputGetBatteryInformation( controllerIndex, BATTERY_DEVTYPE_HEADSET, &batteryInformation );
	if ( dwResult==ERROR_SUCCESS )
	{
		BatteryType batteryType;
		if ( xinputBatteryTypeToBatteryType(batteryInformation.BatteryType, batteryType) )
			setBatteryInformation( Battery_Headset, batteryType, batteryInformation.BatteryLevel );
		else
			clearBatteryInformation( Battery_Headset );
	}
	else
	{
		clearBatteryInformation( Battery_Headset );
	}
#endif
}

// Return false if the device is either unavailable/disconnected or of the wired kind
bool Controller::xinputBatteryTypeToBatteryType( BYTE xinputBatteryType, Controller::BatteryType& batteryType )
{
#ifndef XINPUT_USE_9_1_0
	switch ( xinputBatteryType )
	{
		case BATTERY_TYPE_DISCONNECTED:
		case BATTERY_TYPE_WIRED:
			batteryType=BatteryType_Unknown;
			return false;
		case BATTERY_TYPE_ALKALINE : batteryType=BatteryType_Alkaline;
			return true;
		case BATTERY_TYPE_NIMH : batteryType=BatteryType_NiMH;
			return true;
		case BATTERY_TYPE_UNKNOWN : batteryType=BatteryType_Unknown;
			return true;
	}
	assert(false);
#endif
	return false;
}

void Controller::setBatteryInformation( BatteryID batteryID, BatteryType batteryType, BYTE batteryLevel )
{
	mHasBattery[batteryID] = true;
	mBatteryType[batteryID] = batteryType;
	mBatteryLevel[batteryID] = batteryLevel;
}

void Controller::clearBatteryInformation( BatteryID batteryID )
{
	mHasBattery[batteryID] = false;
	mBatteryType[batteryID] = BatteryType_Unknown;
	mBatteryLevel[batteryID] = 0;
}

}